import { HttpException, HttpStatus, Type } from "@nestjs/common";
import { ApiPropertyOptions, IntersectionType, OmitType, PartialType, PickType } from "@nestjs/swagger";
import { Prisma } from "@prisma/client";
import { type2ZodType } from "src/types/use";
import { z } from "zod";
import { RequiredType } from "./required-type.helper";
import { fromZodError } from "zod-validation-error";

type ActionTypes = 'create'|'delete'|'find'|'findAll'|'update';

/**
 * PrismaModel转swagger @ApiProperty初始化参数
 */
export function model2ApiPropertyInit(model:Prisma.DMMF.Model|undefined) {
  const typeMap = {
    'Int': Number,
    'String': String,
    'Boolean': Boolean,
    'DateTime': String
  }
  return function model2ApiProperty(fieldName: string, options?: ApiPropertyOptions & {action?:ActionTypes}) {
    return {
      description: options?.description ?? model?.fields.find(item=>item.name==fieldName)?.documentation,
      /** TODO default为自增情况！ */
      default: options?.default ?? model?.fields.find(item=>item.name==fieldName)?.default,
      /** TODO外联类型 */
      type: options?.type ?? typeMap[model?.fields.find(item=>item.name==fieldName)?.type as any] ?? String,
      /** TODO应该是外联表才用到 */
      isArray: options?.isArray ?? model?.fields.find(item=>item.name==fieldName)?.isList,
      /** 对应model里面的default，只有创建记录的时候可能为false */
      required: options?.required ?? options?.action == 'create' ? (options?.default ? false : true) : true,
    }
  }
}

/**
 * model转ApiProperty参数
 */
export function generateApiProperty(fields: Prisma.DMMF.Model['fields'], action: ActionTypes, modelName) {
  let result = '';
  if (action==='create')
    fields.filter(item=>item.name!='id')
    .forEach(field=> {
      result += `
    @ApiProperty(model2ApiProperty('${field.name}', {action: 'create'}))
    ${field.name}${field.default==undefined?'!':'?'}: ${modelName}['${field.name}'];
      `;
    })
  if (action==='delete')
    fields.filter(item=>item.name==='id')
    .forEach(field=> {
      result += `
    @ApiProperty(model2ApiProperty('${field.name}'))
    ${field.name}!: ${modelName}['${field.name}'];
      `;
    })
  if (action==='find')
    fields.filter(item=>item.name==='id')
    .forEach(field=> {
      result += `
    @ApiProperty(model2ApiProperty('${field.name}'))
    ${field.name}!: ${modelName}['${field.name}'];
      `;
    })
  if (action==='update')
    fields.filter(item=>!['id', 'isDeleted'].includes(item.name))
    .forEach(field=> {
      result += `
    @ApiProperty(model2ApiProperty('${field.name}'))
    ${field.name}?: ${modelName}['${field.name}'];
      `;
    })
  return result.slice(3, -7);
}

/**
 * 过滤掉对象值为undefined的属性
 */
export function filterUndefined (obj: Object) {
  const result = JSON.parse(JSON.stringify(obj));
  Object.keys(result).forEach(key=> {
    if ([undefined, null, NaN].includes(result[key])) delete result[key];
  })
  return result;
}

interface XxxDto {
  new ();
  prototype: any
  [key: string]: any
}

/**
 * 类形式的Dto转Zod对象
 */
export function classDto2Zod<T extends XxxDto>(ModelDto: T): type2ZodType<T['prototype']> {
  const instance = new ModelDto();
  let propertys: Array<string> = getProtoKeys(instance);  
  return z.object(
    propertys.reduce((obj, key)=>{
      obj[key] = instance[key];
      return obj;
    }, {})
  ) as any
}

/**
 * 获取dto类原型链上所有属性名
 */
function getProtoKeys(instance: any) {
  if (!instance?.__proto__) return [];
  return Object.keys(instance||{}).concat(getProtoKeys(instance.__proto__||{}));
}

/**
 * OmitType扩展, 用于兼容Zod生成
 * @param tragetDto class格式Dto
 * @param propertys 要排除的属性keys
 * @returns class
 */
export function DtoOmitType<T, K extends keyof T> (tragetDto: Type<T>, propertys: readonly K[]) {
  const parent = OmitType(tragetDto, propertys);

  /** 
   * tragetDto 无继承情况
   */
  setPropertyOnPrototype(Object.keys(tragetDto.prototype));

  /** 
   * tragetDto 有继承情况
   */
  //@ts-ignore
  if (tragetDto?.__proto__?.name?.endsWith('TypeClass')) {
    setPropertyOnPrototype(Object.keys(tragetDto.prototype.__proto__));
  }

  /**
   * 在parent原型上设置tragetDto原型属性
   * @param keys tragetDto属性列表
   */
  function setPropertyOnPrototype(keys: Array<string>) {
    keys.filter(item => !propertys.includes(item as K)).map(property=> {
      parent.prototype[property] = tragetDto.prototype[property]
    })
  }
  return parent;
}

/**
 * PickType扩展, 用于兼容Zod生成
 * @param tragetDto class格式Dto
 * @param propertys 要提取的属性keys
 * @returns class
 */
export function DtoPickType<T, K extends keyof T> (tragetDto: Type<T>, propertys: readonly K[]) {
  const parent = PickType(tragetDto, propertys);

  /** 
   * tragetDto 无继承情况
   */
  setPropertyOnPrototype(Object.keys(tragetDto.prototype));

  /** 
   * tragetDto 有继承情况
   */
  //@ts-ignore
  if (tragetDto?.__proto__?.name?.endsWith('TypeClass')) {
    setPropertyOnPrototype(Object.keys(tragetDto.prototype.__proto__));
  }

  /**
   * 在parent原型上设置tragetDto原型属性
   * @param keys tragetDto属性列表
   */
  function setPropertyOnPrototype(keys: Array<string>) {
    keys.filter(item => propertys.includes(item as K)).map(property=> {
      parent.prototype[property] = tragetDto.prototype[property]
    })
  }
  return parent;
}

/**
 * PartialType扩展, 用于兼容Zod生成
 * @param tragetDto class格式Dto
 * @returns class
 */
export function DtoPartialType<T> (tragetDto: Type<T>) {
  const parent = PartialType(tragetDto);

  /** 
   * tragetDto 无继承情况
   */
  setPropertyOnPrototype(Object.keys(tragetDto.prototype));

  /** 
   * tragetDto 有继承情况
   */
  //@ts-ignore
  if (tragetDto?.__proto__?.name?.endsWith('TypeClass')) {
    setPropertyOnPrototype(Object.keys(tragetDto.prototype.__proto__));
  }

  /**
   * 在parent原型上设置tragetDto原型属性
   * @param keys tragetDto属性列表
   */
  function setPropertyOnPrototype(keys: Array<string>) {
    keys.map(property=> {
      parent.prototype[property] = tragetDto.prototype[property].optional()
    })
  }
  return parent;
}

/**
 * 使Dto属性变为必选
 * @param tragetDto class格式Dto
 * @returns class
 */
export function DtoRequiredType<T> (tragetDto: Type<T>) {
  const parent = RequiredType(tragetDto);

  /** 
   * tragetDto 无继承情况
   */
  setPropertyOnPrototype(Object.keys(tragetDto.prototype));

  /** 
   * tragetDto 有继承情况
   */
  //@ts-ignore
  if (tragetDto?.__proto__?.name?.endsWith('TypeClass')) {
    setPropertyOnPrototype(Object.keys(tragetDto.prototype.__proto__));
  }

  /**
   * 在parent原型上设置tragetDto原型属性
   * @param keys tragetDto属性列表
   */
  function setPropertyOnPrototype(keys: Array<string>) {
    keys.map(property=> {
      parent.prototype[property] = tragetDto.prototype[property].unwrap();
    })
  }
  return parent;
}


/**
 * IntersectionType扩展, 用于兼容Zod生成(如果属性重复, 后面的@ApiProp参数会覆盖前面的)
 * @param tragetDto class格式Dto
 * @returns class
 */
export function DtoIntersectionType<T extends Type[]> (...tragetDtos: T) {
  const parent = IntersectionType(...tragetDtos);

  tragetDtos.forEach(tragetDto => {
    /** 
     * tragetDto 无继承情况
     */
    setPropertyOnPrototype(tragetDto, Object.keys(tragetDto.prototype));

    /** 
     * tragetDto 有继承情况
     */
    //@ts-ignore
    if (tragetDto?.__proto__?.name?.endsWith('TypeClass')) {
      setPropertyOnPrototype(tragetDto, Object.keys(tragetDto.prototype.__proto__));
    }
  })

  /**
   * 在parent原型上设置tragetDto原型属性
   * @param keys tragetDto属性列表
   */
  function setPropertyOnPrototype(tragetDto: T[number], keys: Array<string>) {
    keys.map(property=> {
      parent.prototype[property] = tragetDto.prototype[property]
    })
  }
  return parent;
}

/**
 * 解析cookie
 */
export function parseCookie(cookies: string): BaseObj<string> {
  var obj = {};
  
  if (typeof cookies === 'string') {
      // 使用分号进行切割
      var arr = cookies.split(';');
      
      for (var i = 0; i < arr.length; i++) {
          var pair = arr[i].trim().split('=');
          
          // 去除空格并赋值到obj对象上
          obj[pair[0]] = decodeURIComponent(pair[1]);
      }
  }
  
  return obj;
}


/**
 * 格式化日期
 * @param value 指定日期
 * @param format 格式化的规则
 * @example
 * formatDate();
 * formatDate(new Date());
 * formatDate(1603264465956);
 * formatDate(1603264465956, 'h:m:s');
 * formatDate(1603264465956, 'Y年M月D日');
 */
export function formatDate(value: string | number | Date = Date.now(), format = 'Y-M-D h:m:s') {
  if (['null', null, 'undefined', undefined, ''].includes(value as any)) return '';
  // ios 和 mac 系统中，带横杆的字符串日期是格式不了的，这里做一下判断处理
  if (typeof value === 'string' && new Date(value).toString() === 'Invalid Date') {
    value = value.replace(/-/g, '/');
  }
  const formatNumber = (n: number) => `0${n}`.slice(-2);
  const date = new Date(value);
  const formatList = ['Y', 'M', 'D', 'h', 'm', 's'];
  const resultList: Array<string> = [];
  resultList.push(date.getFullYear().toString());
  resultList.push(formatNumber(date.getMonth() + 1));
  resultList.push(formatNumber(date.getDate()));
  resultList.push(formatNumber(date.getHours()));
  resultList.push(formatNumber(date.getMinutes()));
  resultList.push(formatNumber(date.getSeconds()));
  for (let i = 0; i < resultList.length; i++) {
    format = format.replace(formatList[i], resultList[i]);
  }
  return format;
}

/**
 * 校验、解析类型, 校验失败抛出http异常
 * @param zodType zod类型
 * @param data 数据
 * @returns 解析后的数据
 */
export function safeParse<T extends z.ZodType>(zodType: T, data: any) {
  const result = zodType.safeParse(data);
  /** 失败抛出异常 */
  if (!result.success) throw new HttpException(fromZodError(result.error), HttpStatus.EXPECTATION_FAILED); //TODO code
  return result.data as z.infer<T>;
}

/**
 * 从host提取出n（默认3）级域名
 * @example getSecondDomainFromHost('localhost:8000') => undefined
 * @example getSecondDomainFromHost('172.16.10.180:9000') => undefined
 * @example getSecondDomainFromHost('amis.test.lammnu.cn', 3) => test.lammnu.cn
 */
export function getDomainFromHost(
  host: string,
  /** 几级域名 */
  scale: 2 | 3 | 4 = 3,
) {
  let hostWithoutPort = host;
  const portIndex = host.indexOf(':');
  if (portIndex > -1) {
    hostWithoutPort = host.substring(0, portIndex);
  }
  if (hostWithoutPort.endsWith('localhost')) {
    return undefined;
  } else if (/^\d{2,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostWithoutPort)) {
    return undefined;
  } else if (hostWithoutPort.endsWith('.com.cn')) {
    const list = hostWithoutPort.split('.');
    return list.slice(-1 * (scale + 1), list.length).join('.');
  } else  {
    const list = hostWithoutPort.split('.');
    return list.slice(-1 * scale, list.length).join('.');
  }
}